'use strict';
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const moment = require('moment');
const assert = require('assert');
const random = require('random-string');
// const bcrypt = require('bcryptjs');


class DesCipher {
  /**
   * 创建译码对象
   * @param {String} key key
   * @param {String} iv iv
   */
  constructor(key, iv) {
    this.key = key;
    this.iv  = iv;
  }
  /**
  * 加密
  * @param {String} data 加密数据
  * @return {String} 加密后内容
  */
  encrypt(data) {
    const cipher = crypto.createCipheriv('des', this.key, this.iv);
    const clearEncoding = 'utf8';
    const cipherEncoding = 'base64';
    const cipherChunks = [];
    cipher.setAutoPadding(true);
    cipherChunks.push(cipher.update(data, clearEncoding, cipherEncoding));
    cipherChunks.push(cipher.final(cipherEncoding));
    return cipherChunks.join('');
  }

  /**
  * 解密
  * @param {String} data 待解密的字符串
  * @return {String} 加密后内容
  */
  decrypt(data) {
    const clearEncoding = 'utf8';
    const cipherEncoding = 'base64';
    const cipherChunks = [];
    this.cipher.setAutoPadding(true);
    cipherChunks.push(this.cipher.update(data, cipherEncoding, clearEncoding));
    cipherChunks.push(this.cipher.final(clearEncoding));
    return cipherChunks.join('');
  }
}

module.exports = {
  randomString(num, isNum = false) {
    if (isNum) {
      return random({ length: num, letters: false });
    }
    return random({ length: num });
  },
  async sendMsg(subject, text) {
    const data = {
      from: 'xx', // 发件地址
      to  : 'xx', // 收件列表
      subject,
      text,
    };
    return this.app.email.send(data);
  },
  async alarm(str, name, uid, time = 1) {
    const data = {
      from   : 'xx', // 发件地址
      to     : 'xx', // 收件列表
      subject: '异常报警', // 标题
      // text和html两者只支持一种
      text   : `uid: ${uid}\nname: ${name}\n次数:${time}\n信息:${str}`, // 信息
    };
    if (time === 1) {
      await this.app.email.send(data);
    } else {
      let t = await this.app.redis.incr(`alarm:${name}:${uid}`) || '0';
      t = Number(t);
      if (t === time) {
        await this.app.redis.set(`alarm:${name}:${uid}`, 0);
        await this.app.email.send(data);
      }
    }
  },
  /**
   * 封装处理器的异常处理
   * @param {String} controllerName 处理器名字
   * @param {Method} method service方法
   * @param {Number} time 异常次数报警
   */
  async controllerOnErr(controllerName, method = () => '', time = 1) {
    const { ctx } = this;
    try {
      ctx.body = await method();
    } catch (err) {
      if (err instanceof assert.AssertionError) {
        ctx.quickBody(3, err.message);
        return;
      }
      const str = `用户${ctx.user.uid}执行${controllerName}方法异常 err: ${err.stack ? err.stack : err}`;
      ctx.logger.warn(str);
      ctx.quickBody(1);
      this.alarm(str, controllerName, ctx.user.uid, time);
    }
  },

  /**
   * 时间字符串
   * @param {Number} time 时间
   * @return {String} 时间字符串
   */
  relativeTime(time) {
    return moment(new Date(time * 1000)).fromNow();
  },
  /**
   * 写入临时文件夹中
   * @param {ReadableStream} readableStream 读流
   * @param {String} fileExtension 路径
   * @param {String} event 监听某一事件作为返回
   * @return {Promise<{filePath: String, fileHash: String}>} filePath 文件路径, fileHash  文件md5值
   */
  writeToTempPlace(readableStream, fileExtension, event = 'close') {
    const md5sum = crypto.createHash('md5');
    const filePath = path.resolve(this.app.config.messagePublish.tempPlace, `${this.toTimeStamp()}${fileExtension}`);
    const writeStream = fs.createWriteStream(filePath);
    return new Promise((res, rej) => {
      readableStream.pipe(writeStream);
      readableStream.on('data', function(chunk) {
        md5sum.update(chunk);
      });
      writeStream.on(event, () => {
        res({
          filePath,
          fileHash: md5sum.digest('hex'),
        });
      });
      readableStream.on('error', err => {
        rej(err);
      });
      writeStream.on('error', err => {
        rej(err);
      });
    });
  },

  /**
   * 判断是否是自然数
   * @param {Number} num 数字
   * @param {Boolean} needPositive 是否需要是正整数
   * @return {Boolean} 是否是自然数
   */
  judgeNaturalNumber(num, needPositive = false) {
    return Number.isInteger(num) && num >= 0 && (!needPositive || num > 0);
  },

  DesCipher,
  /**
   * 获取时间戳
   * @param {Date} date 时间
   * @return {Integer} 时间戳
   */
  toTimeStamp(date = new Date()) {
    if (typeof date  === 'string') {
      date = new Date(date);
    }
    if (isNaN(date)) {
      date = new Date();
    }
    return parseInt(Number(date) / 1000, 10);
  },

  /**
   * 校验参数
   * @description 不应该在trycatch中使用这个函数，全局捕获异常应放行异常码为422的异常
   * @param {Object} rule 校验规则 参照parameter 包
   * @param {Object} value 校验参数 如ctx.request.body
   * @param {Method} extraRule 额外自定义规则
   * @return {Object} extraRule校验返回
   * @example
   * ```
   * // 示例1： 需要对参数进行额外校验
   * ctx.helper.validate(rule1, ctx.query, (query) => {
   *   if(query.para1 !== '11') throw { msg: 'para1 不等于 11 ！' };
   * })
   * ```
   *
   * @example
   * ```
   * //示例2: 需要对参数进行默认处理 注 result为关键字 不可添加
   * const { page, size } = ctx.helper.validate(rule1, ctx.query, (query) => {
   *  return {
   *    page: +query.page,
   *    size: +query.size,
   *  }
   * })
   * ```
   */
  validate(rule = {}, value, extraRule = () => {}) {
    const { ctx } = this;
    try {
      if (!value) {
        value = ctx.request.body;
      }
      ctx.validate(rule, value);
      const extraRes = extraRule(value);
      return Object.assign({}, value, extraRes);
    } catch (err) {
      ctx.logger.warn(`参数校验失败 err ${JSON.stringify(err)}`);
      let msg = '';
      if (err.msg) {
        msg = err.msg;
      } else if (err.errors) {
        msg = err.errors.map(error => `${error.field} ${error.message}`).join(' and ');
      } else {
        msg = `参数校验异常 ${JSON.stringify(err.stack ? err.stack : err)}`;
      }
      ctx.throw(422, msg);
    }
  },
  /**
   * 以md5的格式创建一个哈希值
   * @param {String} str 待加密字符串
   * @return {Promise<String>} str 加密后字符串
   */
  async genHash(str) {
    const md5 = crypto.createHash('md5');
    return md5.update(str).digest('hex');
  },
  /**
   * 比较hash
   * @param {String} str1 加密前字符串
   * @param {String} hashStr 加密后字符串
   */
  async compare(str1, hashStr) {
    const hashStr2 = await this.genHash(str1);
    const passwordSame = hashStr2 === hashStr;
    if (!passwordSame) {
      this.ctx.logger.warn(`密码不一致,${hashStr2},${hashStr}`);
    }
    return passwordSame;
  },

};
